/*
 *	This file is part of Transdroid <http://www.transdroid.org>
 *
 *	Transdroid is free software: you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation, either version 3 of the License, or
 *	(at your option) any later version.
 *
 *	Transdroid is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *	GNU General Public License for more details.
 *
 *	You should have received a copy of the GNU General Public License
 *	along with Transdroid.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package org.transdroid.daemon.adapters.bitflu;

import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.transdroid.core.gui.log.Log;
import org.transdroid.daemon.Daemon;
import org.transdroid.daemon.DaemonException;
import org.transdroid.daemon.DaemonException.ExceptionType;
import org.transdroid.daemon.DaemonSettings;
import org.transdroid.daemon.IDaemonAdapter;
import org.transdroid.daemon.Priority;
import org.transdroid.daemon.Torrent;
import org.transdroid.daemon.TorrentFile;
import org.transdroid.daemon.TorrentStatus;
import org.transdroid.daemon.task.AddByMagnetUrlTask;
import org.transdroid.daemon.task.AddByUrlTask;
import org.transdroid.daemon.task.DaemonTask;
import org.transdroid.daemon.task.DaemonTaskFailureResult;
import org.transdroid.daemon.task.DaemonTaskResult;
import org.transdroid.daemon.task.DaemonTaskSuccessResult;
import org.transdroid.daemon.task.GetFileListTask;
import org.transdroid.daemon.task.GetFileListTaskSuccessResult;
import org.transdroid.daemon.task.RemoveTask;
import org.transdroid.daemon.task.RetrieveTask;
import org.transdroid.daemon.task.RetrieveTaskSuccessResult;
import org.transdroid.daemon.util.HttpHelper;

import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;

/**
 * An adapter that allows for easy access to uTorrent torrent data. Communication is handled via authenticated JSON-RPC
 * HTTP GET requests and responses.
 *
 * @author adrianulrich
 */

// TODO: TransferRates support

public class BitfluAdapter implements IDaemonAdapter {

    private static final String LOG_NAME = "Bitflu daemon";
    private static final String JSON_ROOT = "Bitflu";
    private static final String RPC_TORRENT_LIST = "torrentList";
    private static final String RPC_PAUSE_TORRENT = "pause/";
    private static final String RPC_RESUME_TORRENT = "resume/";
    private static final String RPC_CANCEL_TORRENT = "cancel/";
    private static final String RPC_REMOVE_TORRENT = "wipe/";
    private static final String RPC_TORRENT_FILES = "showfiles-ext/";
    private static final String RPC_START_DOWNLOAD = "startdownload/";

    private DaemonSettings settings;
    private DefaultHttpClient httpclient;

    /**
     * Initialises an adapter that provides operations to the Bitflu web interface
     */
    public BitfluAdapter(DaemonSettings settings) {
        this.settings = settings;
    }

    @Override
    public DaemonTaskResult executeTask(Log log, DaemonTask task) {

        try {
            switch (task.getMethod()) {
                case Retrieve:
                    // Request all torrents from server
                    JSONObject result = makeBitfluRequest(log, RPC_TORRENT_LIST);
                    return new RetrieveTaskSuccessResult((RetrieveTask) task,
                            parseJsonRetrieveTorrents(result.getJSONArray(JSON_ROOT)), null);
                case Pause:
                    makeBitfluRequest(log, RPC_PAUSE_TORRENT + task.getTargetTorrent().getUniqueID());
                    return new DaemonTaskSuccessResult(task);
                case Resume:
                    makeBitfluRequest(log, RPC_RESUME_TORRENT + task.getTargetTorrent().getUniqueID());
                    return new DaemonTaskSuccessResult(task);
                case Remove:
                    // Remove a torrent
                    RemoveTask removeTask = (RemoveTask) task;
                    String removeUriBase = RPC_CANCEL_TORRENT;

                    if (removeTask.includingData()) {
                        removeUriBase = RPC_REMOVE_TORRENT;
                    }
                    makeBitfluRequest(log, removeUriBase + task.getTargetTorrent().getUniqueID());
                    return new DaemonTaskSuccessResult(task);
                case GetFileList:
                    JSONObject jfiles =
                            makeBitfluRequest(log, RPC_TORRENT_FILES + task.getTargetTorrent().getUniqueID());
                    return new GetFileListTaskSuccessResult((GetFileListTask) task,
                            parseJsonShowFilesTorrent(jfiles.getJSONArray(JSON_ROOT)));
                case AddByUrl:
                    String url = URLEncoder.encode(((AddByUrlTask) task).getUrl(), "UTF-8");
                    makeBitfluRequest(log, RPC_START_DOWNLOAD + url);
                    return new DaemonTaskSuccessResult(task);
                case AddByMagnetUrl:
                    String magnet = URLEncoder.encode(((AddByMagnetUrlTask) task).getUrl(), "UTF-8");
                    makeBitfluRequest(log, RPC_START_DOWNLOAD + magnet);
                    return new DaemonTaskSuccessResult(task);
                default:
                    return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.MethodUnsupported,
                            task.getMethod() + " is not supported by " + getType()));
            }
        } catch (JSONException e) {
            return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.ParsingFailed, e.toString()));
        } catch (DaemonException e) {
            return new DaemonTaskFailureResult(task, e);
        } catch (UnsupportedEncodingException e) {
            return new DaemonTaskFailureResult(task,
                    new DaemonException(ExceptionType.MethodUnsupported, e.toString()));
        }
    }

    private JSONObject makeBitfluRequest(Log log, String addToUrl) throws DaemonException {

        try {

            // Initialise the HTTP client
            initialise();

            // TLog.d(LOG_NAME, "Request to: "+ buildWebUIUrl() + addToUrl);

            // Make request
            HttpGet httpget = new HttpGet(buildWebUIUrl() + addToUrl);
            HttpResponse response = httpclient.execute(httpget);

            // Read JSON response
            InputStream instream = response.getEntity().getContent();
            String result = HttpHelper.convertStreamToString(instream);
            int httpstatus = response.getStatusLine().getStatusCode();

            if (httpstatus != 200) {
                throw new DaemonException(ExceptionType.UnexpectedResponse,
                        "Invalid reply from server, http status code: " + httpstatus);
            }

            if (result.equals("")) { // Empty responses are ok: add fake json content
                result = "empty_response";
            }

            JSONObject json = new JSONObject("{ \"" + JSON_ROOT + "\" : " + result + "}");

            instream.close();
            return json;

        } catch (DaemonException e) {
            throw e;
        } catch (JSONException e) {
            log.d(LOG_NAME, "Error: " + e.toString());
            throw new DaemonException(ExceptionType.ParsingFailed, e.toString());
        } catch (Exception e) {
            log.d(LOG_NAME, "Error: " + e.toString());
            throw new DaemonException(ExceptionType.ConnectionError, e.toString());
        }

    }

    private ArrayList<Torrent> parseJsonRetrieveTorrents(JSONArray results) throws JSONException {
        ArrayList<Torrent> torrents = new ArrayList<>();

        if (results != null) {
            for (int i = 0; i < results.length(); i++) {

                JSONObject tor = results.getJSONObject(i);
                long done_bytes = tor.getLong("done_bytes");
                long total_bytes = tor.getLong("total_bytes");
                float percent = ((float) done_bytes / ((float) total_bytes + 1));

                // @formatter:off
                torrents.add(new Torrent(i,
                        tor.getString("key"),
                        tor.getString("name"),
                        convertBitfluStatus(tor),
                        "/" + settings.getOS().getPathSeperator(),
                        tor.getInt("speed_download"),
                        tor.getInt("speed_upload"),
                        tor.getInt("active_clients"),
                        tor.getInt("active_clients"), // Bitflu doesn't distinguish between seeders and leechers
                        tor.getInt("clients"),
                        tor.getInt("clients"), // Bitflu doesn't distinguish between seeders and leechers
                        tor.getInt("eta"),
                        done_bytes,
                        tor.getLong("uploaded_bytes"),
                        total_bytes,
                        percent,  // Percentage to [0..1]
                        0f,       // Not available
                        null,     // label
                        null,     // Not available
                        null,     // Not available
                        null,   // Not available
                        settings.getType()));
                // @formatter:on
            }
        }
        // Return the list
        return torrents;
    }

    private ArrayList<TorrentFile> parseJsonShowFilesTorrent(JSONArray response) throws JSONException {
        ArrayList<TorrentFile> files = new ArrayList<>();

        if (response != null) {
            for (int i = 0; i < response.length(); i++) {
                JSONObject finfo = response.getJSONObject(i);

                long done_bytes = finfo.getLong("done") * finfo.getLong("chunksize");
                long file_size = finfo.getLong("size");

                if (done_bytes > file_size) { /* Shared chunk */
                    done_bytes = file_size;
                }

                // @formatter:off
                files.add(new TorrentFile(
                        "" + i,
                        finfo.getString("name"),
                        finfo.getString("path"),
                        null, // hmm.. can we have something without file:// ?!
                        file_size,
                        done_bytes,
                        Priority.Normal
                ));
                // @formatter:on
            }
        }

        return files;
    }

    private TorrentStatus convertBitfluStatus(JSONObject obj) throws JSONException {

        if (obj.getInt("paused") != 0) {
            return TorrentStatus.Paused;
        } else if (obj.getLong("done_bytes") == obj.getLong("total_bytes")) {
            return TorrentStatus.Seeding;
        }
        return TorrentStatus.Downloading;
    }

    /**
     * Instantiates an HTTP client with proper credentials that can be used for all HTTP requests.
     *
     * @throws DaemonException On conflicting or missing settings
     */
    private synchronized void initialise() throws DaemonException {
        if(httpclient == null) {
            httpclient = HttpHelper.createStandardHttpClient(settings, true);
        }
    }

    /**
     * Build the URL of the Transmission web UI from the user settings.
     *
     * @return The URL of the RPC API
     */
    private String buildWebUIUrl() {
        String webuiroot = "";
        if (settings.getFolder() != null) {
            webuiroot = settings.getFolder();
        }
        return (settings.getSsl() ? "https://" : "http://") + settings.getAddress() + ":" + settings.getPort() +
                webuiroot + "/";
    }

    @Override
    public Daemon getType() {
        return settings.getType();
    }

    @Override
    public DaemonSettings getSettings() {
        return this.settings;
    }

}
